/*
 * Copyright 2009-2010 the original author or authors.
 *
 * Licensed under the Apache license, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.internna.iwebjtracker.spring.mvc;

import java.util.Map;
import java.util.List;
import java.util.HashMap;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.internna.iwebmvc.dao.DAO;
import org.internna.iwebmvc.utils.Assert;
import org.internna.iwebmvc.crypto.Cipherer;
import org.internna.iwebmvc.model.DomainEntity;
import org.internna.iwebmvc.utils.CollectionUtils;
import org.internna.iwebjtracker.model.DefaultIssue;
import org.internna.iwebjtracker.model.DefaultProject;

import static org.internna.iwebmvc.utils.StringUtils.hasText;

/**
 * Redirects all REST URLs to native ones.
 *
 * @author Jose Noheda
 * @since 1.0
 */
public final class RESTFilter implements Filter {

    static final String RSS_PATH = "/rss.iwebmvc";
    static final String ISSUE_PARAM = "&openIssue=";
    static final String ALIAS_PARAM = "&projectAlias=";
    static final String ISSUE_ALIAS_PARAM = "&issueAlias=";
    static final String PROJECT_PATH = "/index.iwebmvc?openProject=";

    private DAO dao;
    private Cipherer cipherer;

    /**
     * Initializes the JEE filter.
     *
     * @param dao a non null instance
     * @param cipherer a non null instance
     */
    public RESTFilter(DAO dao, Cipherer cipherer) {
        Assert.notNull(dao);
        Assert.notNull(cipherer);
        this.dao = dao;
        this.cipherer = cipherer;
    }

    /**
     * Redirects an URL to the main controller
     * @param request
     * @param response
     * @param chain
     * @throws IOException
     * @throws ServletException
     */
    @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
        HttpServletRequest req = ((HttpServletRequest) request);
        String path = req.getServletPath();
        String redirection = redirectURL(path);
        HttpServletResponse res = (HttpServletResponse) response;
        if (hasText(redirection)) {
            res.sendRedirect(req.getContextPath() + redirection);
        } else {
            res.sendError(res.SC_NOT_FOUND);
        }
    }

    /**
     * Detects the kind of redirection needed and delegates the URL generation logic downwards.
     *
     * @param path
     * @return
     */
    protected String redirectURL(String path) {
        if (hasText(path)) {
            return path.startsWith("/project/") ? redirectProject(path) :
                path.startsWith("/issue/") ? redirectIssue(path) : redirectRSS(path);
        }
        return null;
    }

    /**
     * Generates a valid RSS controller URL that possibly includes the user, project and/or issue.
     *
     * @param rss any non null string
     * @return
     */
    protected String redirectRSS(String rss) {
        if ("/rss".equals(rss) || "/rss/".equals(rss)) {
            return RSS_PATH;
        } else if (rss.startsWith("/rss/user/")) {
            return RSS_PATH + "?user=" + rss.substring(rss.lastIndexOf('/') + 1);
        } else if (isIssue(rss)) {
            String id = getIdFromAlias(DefaultIssue.FIND_BY_ALIAS, getIssueNumber(rss.substring(5)));
            if (hasText(id)) {
                return RSS_PATH + "?issue=" + cipherer.encrypt(id);
            }
        } else {
            String id = getIdFromAlias(DefaultProject.FIND_BY_ALIAS, rss.substring(5));
            if (hasText(id)) {
                return RSS_PATH + "?project=" + cipherer.encrypt(id);
            }
        }
        return null;
    }

    /**
     * Detects issue alias by checking the number after the project name.
     *
     * @param rssPath a non null string
     * @return
     */
    protected boolean isIssue(String rssPath) {
        return getIssueNumber(rssPath) != null;
    }

    /**
     * Returns the issue number from an alias.
     *
     * @param rssPath any non null string
     * @return any
     */
    protected Integer getIssueNumber(String rssPath) {
        if (rssPath.indexOf('-') > 0) {
            try {
                return Integer.valueOf(rssPath.substring(rssPath.lastIndexOf('-') + 1));
            } catch (NumberFormatException ex) {
            }
        }
        return null;
    }

    /**
     * Generates a valid URL with the encrypted project.
     *
     * @param project any non null path like /project/[PROJECT-ALIAS]
     * @return
     */
    protected String redirectProject(String project) {
        String path = project.substring(9);
        if (path.indexOf('/') < 0) {
            String id = getIdFromAlias(DefaultProject.FIND_BY_ALIAS, path);
            if (hasText(id)) {
                return PROJECT_PATH + cipherer.encrypt(id) + ALIAS_PARAM + path;
            }
        }
        return null;
    }

    /**
     * Generates a valid URL with the encrypted issue.
     *
     * @param issue any non null path like /issue/[ISSUE-ALIAS]
     * @return any
     */
    protected String redirectIssue(String issue) {
        String path = issue.substring(7);
        String id = getIdFromAlias(DefaultIssue.FIND_BY_ALIAS, getIssueNumber(path));
        if (hasText(id)) {
            int separator = path.lastIndexOf('-');
            return redirectProject("/project/" + path.substring(0, separator)) + ISSUE_PARAM + cipherer.encrypt(id) + ISSUE_ALIAS_PARAM + path;
        }
        return null;
    }

    /**
     * Loads a project from its alias and returns the UUID.
     *
     * @param alias a non null string
     * @return any
     */
    protected String getIdFromAlias(String query, Object alias) {
        Map<String, Object> params = new HashMap<String, Object>(1);
        params.put("alias", alias);
        List<DomainEntity> prjs = dao.findByNamedQuery(query, 0, 1, params);
        if (CollectionUtils.isNotEmpty(prjs)) {
            return prjs.get(0).getId().toString();
        }
        return null;
    }

    /**
     * Unsupported (managed by Spring)
     */
    @Override public void init(FilterConfig filterConfig) throws ServletException {
        throw new UnsupportedOperationException();
    }

    /**
     * Unsupported (managed by Spring)
     */
    @Override public void destroy() {
        throw new UnsupportedOperationException();
    }

}
